// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/window/dialog_client_view.h"

#include <algorithm>

#include "build/build_config.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/blue_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"

namespace views {

namespace {

    // The group used by the buttons.  This name is chosen voluntarily big not to
    // conflict with other groups that could be in the dialog content.
    const int kButtonGroup = 6666;

#if defined(OS_WIN) || defined(OS_CHROMEOS)
    const bool kIsOkButtonOnLeftSide = true;
#else
    const bool kIsOkButtonOnLeftSide = false;
#endif

    // Returns true if the given view should be shown (i.e. exists and is
    // visible).
    bool ShouldShow(View* view)
    {
        return view && view->visible();
    }

    // Do the layout for a button.
    void LayoutButton(LabelButton* button, gfx::Rect* row_bounds)
    {
        if (!button)
            return;

        const gfx::Size size = button->GetPreferredSize();
        row_bounds->set_width(row_bounds->width() - size.width());
        button->SetBounds(row_bounds->right(), row_bounds->y(),
            size.width(), row_bounds->height());
        row_bounds->set_width(row_bounds->width() - kRelatedButtonHSpacing);
    }

} // namespace

///////////////////////////////////////////////////////////////////////////////
// DialogClientView, public:

DialogClientView::DialogClientView(Widget* owner, View* contents_view)
    : ClientView(owner, contents_view)
    , ok_button_(NULL)
    , cancel_button_(NULL)
    , extra_view_(NULL)
    , footnote_view_(NULL)
    , notified_delegate_(false)
{
}

DialogClientView::~DialogClientView()
{
}

void DialogClientView::AcceptWindow()
{
    // Only notify the delegate once. See |notified_delegate_|'s comment.
    if (!notified_delegate_ && GetDialogDelegate()->Accept(false)) {
        notified_delegate_ = true;
        Close();
    }
}

void DialogClientView::CancelWindow()
{
    // Only notify the delegate once. See |notified_delegate_|'s comment.
    if (!notified_delegate_ && GetDialogDelegate()->Cancel()) {
        notified_delegate_ = true;
        Close();
    }
}

void DialogClientView::UpdateDialogButtons()
{
    const int buttons = GetDialogDelegate()->GetDialogButtons();
    ui::Accelerator escape(ui::VKEY_ESCAPE, ui::EF_NONE);

    if (buttons & ui::DIALOG_BUTTON_OK) {
        if (!ok_button_) {
            ok_button_ = CreateDialogButton(ui::DIALOG_BUTTON_OK);
            if (!(buttons & ui::DIALOG_BUTTON_CANCEL))
                ok_button_->AddAccelerator(escape);
            AddChildView(ok_button_);
        }

        UpdateButton(ok_button_, ui::DIALOG_BUTTON_OK);
    } else if (ok_button_) {
        delete ok_button_;
        ok_button_ = NULL;
    }

    if (buttons & ui::DIALOG_BUTTON_CANCEL) {
        if (!cancel_button_) {
            cancel_button_ = CreateDialogButton(ui::DIALOG_BUTTON_CANCEL);
            cancel_button_->AddAccelerator(escape);
            AddChildView(cancel_button_);
        }

        UpdateButton(cancel_button_, ui::DIALOG_BUTTON_CANCEL);
    } else if (cancel_button_) {
        delete cancel_button_;
        cancel_button_ = NULL;
    }

    // Use the escape key to close the window if there are no dialog buttons.
    if (!has_dialog_buttons())
        AddAccelerator(escape);
    else
        ResetAccelerators();
}

///////////////////////////////////////////////////////////////////////////////
// DialogClientView, ClientView overrides:

bool DialogClientView::CanClose()
{
    if (notified_delegate_)
        return true;

    // The dialog is closing but no Accept or Cancel action has been performed
    // before: it's a Close action.
    if (GetDialogDelegate()->Close()) {
        notified_delegate_ = true;
        GetDialogDelegate()->OnClosed();
        return true;
    }
    return false;
}

DialogClientView* DialogClientView::AsDialogClientView()
{
    return this;
}

const DialogClientView* DialogClientView::AsDialogClientView() const
{
    return this;
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, View overrides:

gfx::Size DialogClientView::GetPreferredSize() const
{
    // Initialize the size to fit the buttons and extra view row.
    int extra_view_padding = 0;
    if (!GetDialogDelegate()->GetExtraViewPadding(&extra_view_padding))
        extra_view_padding = kRelatedButtonHSpacing;
    gfx::Size size(
        (ok_button_ ? ok_button_->GetPreferredSize().width() : 0) + (cancel_button_ ? cancel_button_->GetPreferredSize().width() : 0) + (cancel_button_ && ok_button_ ? kRelatedButtonHSpacing : 0) + (ShouldShow(extra_view_) ? extra_view_->GetPreferredSize().width() : 0) + (ShouldShow(extra_view_) && has_dialog_buttons() ? extra_view_padding : 0),
        0);

    int buttons_height = GetButtonsAndExtraViewRowHeight();
    if (buttons_height != 0) {
        size.Enlarge(0, buttons_height + kRelatedControlVerticalSpacing);
        // Inset the buttons and extra view.
        const gfx::Insets insets = GetButtonRowInsets();
        size.Enlarge(insets.width(), insets.height());
    }

    // Increase the size as needed to fit the contents view.
    // NOTE: The contents view is not inset on the top or side client view edges.
    gfx::Size contents_size = contents_view()->GetPreferredSize();
    size.Enlarge(0, contents_size.height());
    size.set_width(std::max(size.width(), contents_size.width()));

    // Increase the size as needed to fit the footnote view.
    if (ShouldShow(footnote_view_)) {
        gfx::Size footnote_size = footnote_view_->GetPreferredSize();
        if (!footnote_size.IsEmpty())
            size.set_width(std::max(size.width(), footnote_size.width()));

        int footnote_height = footnote_view_->GetHeightForWidth(size.width());
        size.Enlarge(0, footnote_height);
    }

    return size;
}

void DialogClientView::Layout()
{
    gfx::Rect bounds = GetContentsBounds();

    // Layout the footnote view.
    if (ShouldShow(footnote_view_)) {
        const int height = footnote_view_->GetHeightForWidth(bounds.width());
        footnote_view_->SetBounds(bounds.x(), bounds.bottom() - height,
            bounds.width(), height);
        if (height != 0)
            bounds.Inset(0, 0, 0, height);
    }

    // Layout the row containing the buttons and the extra view.
    if (has_dialog_buttons() || ShouldShow(extra_view_)) {
        bounds.Inset(GetButtonRowInsets());
        const int height = GetButtonsAndExtraViewRowHeight();
        gfx::Rect row_bounds(bounds.x(), bounds.bottom() - height,
            bounds.width(), height);
        if (kIsOkButtonOnLeftSide) {
            LayoutButton(cancel_button_, &row_bounds);
            LayoutButton(ok_button_, &row_bounds);
        } else {
            LayoutButton(ok_button_, &row_bounds);
            LayoutButton(cancel_button_, &row_bounds);
        }
        if (extra_view_) {
            int custom_padding = 0;
            if (has_dialog_buttons() && GetDialogDelegate()->GetExtraViewPadding(&custom_padding)) {
                // The call to LayoutButton() will already have accounted for some of
                // the padding.
                custom_padding -= kRelatedButtonHSpacing;
                row_bounds.set_width(row_bounds.width() - custom_padding);
            }
            row_bounds.set_width(std::min(row_bounds.width(),
                extra_view_->GetPreferredSize().width()));
            extra_view_->SetBoundsRect(row_bounds);
        }

        if (height > 0)
            bounds.Inset(0, 0, 0, height + kRelatedControlVerticalSpacing);
    }

    // Layout the contents view to the top and side edges of the contents bounds.
    // NOTE: The local insets do not apply to the contents view sides or top.
    const gfx::Rect contents_bounds = GetContentsBounds();
    contents_view()->SetBounds(contents_bounds.x(), contents_bounds.y(),
        contents_bounds.width(), bounds.bottom() - contents_bounds.y());
}

bool DialogClientView::AcceleratorPressed(const ui::Accelerator& accelerator)
{
    DCHECK_EQ(accelerator.key_code(), ui::VKEY_ESCAPE);
    Close();
    return true;
}

void DialogClientView::ViewHierarchyChanged(
    const ViewHierarchyChangedDetails& details)
{
    ClientView::ViewHierarchyChanged(details);
    if (details.is_add && details.child == this) {
        UpdateDialogButtons();
        CreateExtraView();
        CreateFootnoteView();
    } else if (!details.is_add && details.child != this) {
        if (details.child == ok_button_)
            ok_button_ = NULL;
        if (details.child == cancel_button_)
            cancel_button_ = NULL;
    }
}

void DialogClientView::OnNativeThemeChanged(const ui::NativeTheme* theme)
{
    // The old dialog style needs an explicit background color, while the new
    // dialog style simply inherits the bubble's frame view color.
    const DialogDelegate* dialog = GetDialogDelegate();

    if (dialog && !dialog->UseNewStyleForThisDialog()) {
        set_background(views::Background::CreateSolidBackground(GetNativeTheme()->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
    }
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, ButtonListener implementation:

void DialogClientView::ButtonPressed(Button* sender, const ui::Event& event)
{
    // Check for a valid delegate to avoid handling events after destruction.
    if (!GetDialogDelegate())
        return;

    if (sender == ok_button_)
        AcceptWindow();
    else if (sender == cancel_button_)
        CancelWindow();
    else
        NOTREACHED();
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, protected:

DialogClientView::DialogClientView(View* contents_view)
    : ClientView(NULL, contents_view)
    , ok_button_(NULL)
    , cancel_button_(NULL)
    , extra_view_(NULL)
    , footnote_view_(NULL)
    , notified_delegate_(false)
{
}

DialogDelegate* DialogClientView::GetDialogDelegate() const
{
    return GetWidget()->widget_delegate()->AsDialogDelegate();
}

void DialogClientView::CreateExtraView()
{
    if (extra_view_)
        return;

    extra_view_ = GetDialogDelegate()->CreateExtraView();
    if (extra_view_) {
        extra_view_->SetGroup(kButtonGroup);
        AddChildView(extra_view_);
    }
}

void DialogClientView::CreateFootnoteView()
{
    if (footnote_view_)
        return;

    footnote_view_ = GetDialogDelegate()->CreateFootnoteView();
    if (footnote_view_)
        AddChildView(footnote_view_);
}

void DialogClientView::ChildPreferredSizeChanged(View* child)
{
    if (child == footnote_view_ || child == extra_view_)
        Layout();
}

void DialogClientView::ChildVisibilityChanged(View* child)
{
    ChildPreferredSizeChanged(child);
}

////////////////////////////////////////////////////////////////////////////////
// DialogClientView, private:

LabelButton* DialogClientView::CreateDialogButton(ui::DialogButton type)
{
    const base::string16 title = GetDialogDelegate()->GetDialogButtonLabel(type);
    LabelButton* button = NULL;
    if (GetDialogDelegate()->UseNewStyleForThisDialog() && GetDialogDelegate()->GetDefaultDialogButton() == type && GetDialogDelegate()->ShouldDefaultButtonBeBlue()) {
        button = new BlueButton(this, title);
    } else {
        button = new LabelButton(this, title);
        button->SetStyle(Button::STYLE_BUTTON);
    }
    button->SetFocusable(true);

    const int kDialogMinButtonWidth = 75;
    button->SetMinSize(gfx::Size(kDialogMinButtonWidth, 0));
    button->SetGroup(kButtonGroup);
    return button;
}

void DialogClientView::UpdateButton(LabelButton* button,
    ui::DialogButton type)
{
    DialogDelegate* dialog = GetDialogDelegate();
    button->SetText(dialog->GetDialogButtonLabel(type));
    button->SetEnabled(dialog->IsDialogButtonEnabled(type));
    button->SetIsDefault(type == dialog->GetDefaultDialogButton());
}

int DialogClientView::GetButtonsAndExtraViewRowHeight() const
{
    int extra_view_height = ShouldShow(extra_view_) ? extra_view_->GetPreferredSize().height() : 0;
    int buttons_height = std::max(
        ok_button_ ? ok_button_->GetPreferredSize().height() : 0,
        cancel_button_ ? cancel_button_->GetPreferredSize().height() : 0);
    return std::max(extra_view_height, buttons_height);
}

gfx::Insets DialogClientView::GetButtonRowInsets() const
{
    // NOTE: The insets only apply to the buttons, extra view, and footnote view.
    return GetButtonsAndExtraViewRowHeight() == 0 ? gfx::Insets() : gfx::Insets(0, kButtonHEdgeMarginNew, kButtonVEdgeMarginNew, kButtonHEdgeMarginNew);
}

void DialogClientView::Close()
{
    GetWidget()->Close();
    GetDialogDelegate()->OnClosed();
}

} // namespace views
